#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include "wget.h"
#define ISSPACE(x)  isspace ((unsigned char)(x))
#define ISDIGIT(x)  isdigit ((unsigned char)(x))
# define xstrdup strdup
#define CLEANDUP(x) (contains_unsafe (x) ? encode_string (x) : xstrdup (x))

struct urlinfo
{
  char *url;			/* Unchanged URL */
  uerr_t proto;			/* URL protocol */
  char *host;			/* Extracted hostname */
  unsigned short port;
  char ftp_type;
  char *path, *dir, *file;	/* Path, as well as dir and file
				   (properly decoded) */
  char *user, *passwd;		/* Username and password */
  struct urlinfo *proxy;	/* The exact string to pass to proxy
				   server */
  char *referer;		/* The source from which the request
				   URI was obtained */
  char *local;			/* The local filename of the URL
				   document */
};

#define DEFAULT_HTTP_PORT 80
#define DEFAULT_FTP_PORT 21
#ifndef WINDOWS
# define URL_UNSAFE " <>\"#%{}|\\^~[]`@:\033"
#else  /* WINDOWS */
# define URL_UNSAFE " <>\"%{}|\\^[]`\033"
#endif /* WINDOWS */

int
contains_unsafe (const char *s)
{
  for (; *s; s++)
    if (strchr (URL_UNSAFE, *s))
      return 1;
  return 0;
}
char *
encode_string (const char *s)
{
  const char *b;
  char *p, *res;
  int i;

  b = s;
  for (i = 0; *s; s++, i++)
    if (strchr (URL_UNSAFE, *s))
      i += 2; /* Two more characters (hex digits) */
  res = (char *)xmalloc (i + 1);
  s = b;
  for (p = res; *s; s++)
    if (strchr (URL_UNSAFE, *s))
      {
	const unsigned char c = *s;
	*p++ = '%';
	*p++ = HEXD2ASC (c >> 4);
	*p++ = HEXD2ASC (c & 0xf);
      }
    else
      *p++ = *s;
  *p = '\0';
  return res;
}
#define URL_CLEANSE(s) do			\
{						\
  if (contains_unsafe (s))			\
    {						\
      char *uc_tmp = encode_string (s);		\
      free (s);					\
      (s) = uc_tmp;				\
    }						\
} while (0)


#define ASC2HEXD(x) (((x) >= '0' && (x) <= '9') ?               \
		     ((x) - '0') : (toupper(x) - 'A' + 10))

#define ISXDIGIT(x) isxdigit ((unsigned char)(x))
#define DOTP(x) ((*(x) == '.') && (!*(x + 1)))
/* Is a directory ".."?  */
#define DDOTP(x) ((*(x) == '.') && (*(x + 1) == '.') && (!*(x + 2)))



static char *protostrings[] =
{
  "cid:",
  "clsid:",
  "file:",
  "finger:",
  "ftp:",
  "gopher:",
  "hdl:",
  "http:",
  "https:",
  "ilu:",
  "ior:",
  "irc:",
  "java:",
  "javascript:",
  "lifn:",
  "mailto:",
  "mid:",
  "news:",
  "nntp:",
  "path:",
  "prospero:",
  "rlogin:",
  "service:",
  "shttp:",
  "snews:",
  "stanf:",
  "telnet:",
  "tn3270:",
  "wais:",
  "whois++:",
  NULL
};




void *xmalloc(int size)
{
  void *p;
  p = malloc(size);
  if(!p)
    printf("malloc error");
  return p;
}

int
skip_proto (const char *url)
{
  char **s;
  int l;

  for (s = protostrings; *s; s++)
    if (!strncasecmp (*s, url, strlen (*s)))
      break;
  if (!*s)
    return 0;
  l = strlen (*s);
  /* HTTP and FTP protocols are expected to yield exact host names
     (i.e. the `//' part must be skipped, too).  */
  if (!strcmp (*s, "http:") || !strcmp (*s, "ftp:"))
    l += 2;
  return l;
}

int
skip_url (const char *url)
{
  int i;

  if (toupper (url[0]) == 'U'
      && toupper (url[1]) == 'R'
      && toupper (url[2]) == 'L'
      && url[3] == ':')
    {
      /* Skip blanks.  */
      for (i = 4; url[i] && ISSPACE (url[i]); i++);
      return i;
    }
  else
    return 0;
}

char *
strdupdelim (const char *beg, const char *end)
{
  char *res = (char *)xmalloc (end - beg + 1);
  memcpy (res, beg, end - beg);
  res[end - beg] = '\0';
  return res;
}


static void
parse_dir (const char *path, char **dir, char **file)
{
  int i, l;

  for (i = l = strlen (path); i && path[i] != '/'; i--);
  if (!i && *path != '/')   /* Just filename */
    {
      if (DOTP (path) || DDOTP (path))
	{
	  *dir = xstrdup (path);
	  *file = xstrdup ("");
	}
      else
	{
	  *dir = xstrdup ("");     /* This is required because of FTP */
	  *file = xstrdup (path);
	}
    }
  else if (!i)                 /* /filename */
    {
      if (DOTP (path + 1) || DDOTP (path + 1))
	{
	  *dir = xstrdup (path);
	  *file = xstrdup ("");
	}
      else
	{
	  *dir = xstrdup ("/");
	  *file = xstrdup (path + 1);
	}
    }
  else /* Nonempty directory with or without a filename */
    {
      if (DOTP (path + i + 1) || DDOTP (path + i + 1))
	{
	  *dir = xstrdup (path);
	  *file = xstrdup ("");
	}
      else
	{
	  *dir = strdupdelim (path, path + i);
	  *file = strdupdelim (path + i + 1, path + l + 1);
	}
    }
}


static uerr_t
parse_uname (const char *url, char **user, char **passwd)
{
  int l;
  const char *p, *col;
  char **where;

  *user = NULL;
  *passwd = NULL;
  url += skip_url (url);
  /* Look for end of protocol string.  */
  l = skip_proto (url);
  if (!l)
    return URLUNKNOWN;
  /* Add protocol offset.  */
  url += l;
  /* Is there an `@' character?  */
  for (p = url; *p && *p != '/'; p++)
    if (*p == '@')
      break;
  /* If not, return.  */
  if (*p != '@')
    return URLOK;
  /* Else find the username and password.  */
  for (p = col = url; *p != '@'; p++)
    {
      if (*p == ':' && !*user)
	{
	  *user = (char *)xmalloc (p - url + 1);
	  memcpy (*user, url, p - url);
	  (*user)[p - url] = '\0';
	  col = p + 1;
	}
    }
  /* Decide whether you have only the username or both.  */
  where = *user ? passwd : user;
  *where = (char *)xmalloc (p - col + 1);
  memcpy (*where, col, p - col);
  (*where)[p - col] = '\0';
  return URLOK;
}



static void
decode_string (char *s)
{
  char *p = s;

  for (; *s; s++, p++)
    {
      if (*s != '%')
	*p = *s;
      else
	{
	  /* Do nothing if at the end of the string, or if the chars
	     are not hex-digits.  */
	  if (!*(s + 1) || !*(s + 2)
	      || !(ISXDIGIT (*(s + 1)) && ISXDIGIT (*(s + 2))))
	    {
	      *p = *s;
	      continue;
	    }
	  *p = (ASC2HEXD (*(s + 1)) << 4) + ASC2HEXD (*(s + 2));
	  s += 2;
	}
    }
  *p = '\0';
}



struct proto
{
  char *name;
  uerr_t ind;
  unsigned short port;
};

static char
process_ftp_type (char *path)
{
  int len = strlen (path);

  if (len >= 7
      && !memcmp (path + len - 7, ";type=", 6))
    {
      path[len - 7] = '\0';
      return path[len - 1];
    }
  else
    return '\0';
}

int
skip_uname (const char *url)
{
  const char *p;
  for (p = url; *p && *p != '/'; p++)
    if (*p == '@')
      break;
  /* If a `@' was found before the first occurrence of `/', skip
     it.  */
  if (*p == '@')
    return p - url + 1;
  else
    return 0;
}

static struct proto sup_protos[] =
{
  { "http://", URLHTTP, DEFAULT_HTTP_PORT },
  { "ftp://", URLFTP, DEFAULT_FTP_PORT },
  /*{ "file://", URLFILE, DEFAULT_FTP_PORT },*/
};

void
long_to_string (char *buffer, long number)
{
  char *p;
  int i, l;

  if (number < 0)
    {
      *buffer++ = '-';
      number = -number;
    }
  p = buffer;
  /* Print the digits to the string.  */
  do
    {
      *p++ = number % 10 + '0';
      number /= 10;
    }
  while (number);
  /* And reverse them.  */
  l = p - buffer - 1;
  for (i = l/2; i >= 0; i--)
    {
      char c = buffer[i];
      buffer[i] = buffer[l - i];
      buffer[l - i] = c;
    }
  buffer[l + 1] = '\0';
}
int
numdigit (long a)
{
  int res = 1;
  while ((a /= 10) != 0)
    ++res;
  return res;
}


char *
str_url (const struct urlinfo *u, int hide)
{
  char *res, *host, *user, *passwd, *proto_name, *dir, *file;
  int i, l, ln, lu, lh, lp, lf, ld;

  /* Look for the protocol name.  */
  for (i = 0; i < ARRAY_SIZE (sup_protos); i++)
    if (sup_protos[i].ind == u->proto)
      break;
  if (i == ARRAY_SIZE (sup_protos))
    return NULL;
  proto_name = sup_protos[i].name;
  host = CLEANDUP (u->host);
  dir = CLEANDUP (u->dir);
  file = CLEANDUP (u->file);
  user = passwd = NULL;
  if (u->user)
    user = CLEANDUP (u->user);
  if (u->passwd)
    {
      int i;
      passwd = CLEANDUP (u->passwd);
      if (hide)
	for (i = 0; passwd[i]; i++)
	  passwd[i] = 'x';
    }
  if (u->proto == URLFTP && *dir == '/')
    {
      char *tmp = (char *)xmalloc (strlen (dir) + 3);
      /*sprintf (tmp, "%%2F%s", dir + 1);*/
      *tmp = '%';
      tmp[1] = '2';
      tmp[2] = 'F';
      strcpy (tmp + 3, dir + 1);
      free (dir);
      dir = tmp;
    }

  ln = strlen (proto_name);
  lu = user ? strlen (user) : 0;
  lp = passwd ? strlen (passwd) : 0;
  lh = strlen (host);
  ld = strlen (dir);
  lf = strlen (file);
  res = (char *)xmalloc (ln + lu + lp + lh + ld + lf + 20); /* safe sex */
  /* sprintf (res, "%s%s%s%s%s%s:%d/%s%s%s", proto_name,
     (user ? user : ""), (passwd ? ":" : ""),
     (passwd ? passwd : ""), (user ? "@" : ""),
     host, u->port, dir, *dir ? "/" : "", file); */
  l = 0;
  memcpy (res, proto_name, ln);
  l += ln;
  if (user)
    {
      memcpy (res + l, user, lu);
      l += lu;
      if (passwd)
	{
	  res[l++] = ':';
	  memcpy (res + l, passwd, lp);
	  l += lp;
	}
      res[l++] = '@';
    }
  memcpy (res + l, host, lh);
  l += lh;
  res[l++] = ':';
  long_to_string (res + l, (long)u->port);
  l += numdigit (u->port);
  res[l++] = '/';
  memcpy (res + l, dir, ld);
  l += ld;
  if (*dir)
    res[l++] = '/';
  strcpy (res + l, file);
  free (host);
  free (dir);
  free (file);
  FREE_MAYBE (user);
  FREE_MAYBE (passwd);
  return res;
}



static int
has_proto (const char *url)
{
  char **s;

  url += skip_url (url);
  for (s = protostrings; *s; s++)
    if (strncasecmp (url, *s, strlen (*s)) == 0)
      return 1;
  return 0;
}

void
path_simplify (char *path)
{
  register int i, start, ddot;
  char stub_char;

  if (!*path)
    return;

  /*stub_char = (*path == '/') ? '/' : '.';*/
  stub_char = '/';

  /* Addition: Remove all `./'-s preceding the string.  If `../'-s
     precede, put `/' in front and remove them too.  */
  i = 0;
  ddot = 0;
  while (1)
    {
      if (path[i] == '.' && path[i + 1] == '/')
	i += 2;
      else if (path[i] == '.' && path[i + 1] == '.' && path[i + 2] == '/')
	{
	  i += 3;
	  ddot = 1;
	}
      else
	break;
    }
  if (i)
    strcpy (path, path + i - ddot);

  /* Replace single `.' or `..' with `/'.  */
  if ((path[0] == '.' && path[1] == '\0')
      || (path[0] == '.' && path[1] == '.' && path[2] == '\0'))
    {
      path[0] = stub_char;
      path[1] = '\0';
      return;
    }
  /* Walk along PATH looking for things to compact.  */
  i = 0;
  while (1)
    {
      if (!path[i])
	break;

      while (path[i] && path[i] != '/')
	i++;

      start = i++;

      /* If we didn't find any slashes, then there is nothing left to do.  */
      if (!path[start])
	break;

      /* Handle multiple `/'s in a row.  */
      while (path[i] == '/')
	i++;

      if ((start + 1) != i)
	{
	  strcpy (path + start + 1, path + i);
	  i = start + 1;
	}

      /* Check for trailing `/'.  */
      if (start && !path[i])
	{
	zero_last:
	  path[--i] = '\0';
	  break;
	}

      /* Check for `../', `./' or trailing `.' by itself.  */
      if (path[i] == '.')
	{
	  /* Handle trailing `.' by itself.  */
	  if (!path[i + 1])
	    goto zero_last;

	  /* Handle `./'.  */
	  if (path[i + 1] == '/')
	    {
	      strcpy (path + i, path + i + 1);
	      i = (start < 0) ? 0 : start;
	      continue;
	    }

	  /* Handle `../' or trailing `..' by itself.  */
	  if (path[i + 1] == '.' &&
	      (path[i + 2] == '/' || !path[i + 2]))
	    {
	      while (--start > -1 && path[start] != '/');
	      strcpy (path + start + 1, path + i + 2);
	      i = (start < 0) ? 0 : start;
	      continue;
	    }
	}	/* path == '.' */
    } /* while */

  if (!*path)
    {
      *path = stub_char;
      path[1] = '\0';
    }
}

/* Extract the given URL of the form
   (http:|ftp:)// (user (:password)?@)?hostname (:port)? (/path)?
   1. hostname (terminated with `/' or `:')
   2. port number (terminated with `/'), or chosen for the protocol
   3. dirname (everything after hostname)
   Most errors are handled.  No allocation is done, you must supply
   pointers to allocated memory.
   ...and a host of other stuff :-)

   - Recognizes hostname:dir/file for FTP and
     hostname (:portnum)?/dir/file for HTTP.
   - Parses the path to yield directory and file
   - Parses the URL to yield the username and passwd (if present)
   - Decodes the strings, in case they contain "forbidden" characters
   - Writes the result to struct urlinfo

   If the argument STRICT is set, it recognizes only the canonical
   form.  */
uerr_t
parseurl (const char *url, struct urlinfo *u, int strict)
{
  int i, l, abs_ftp;
  int recognizable;            /* Recognizable URL is the one where
				  the protocol name was explicitly
				  named, i.e. it wasn't deduced from
				  the URL format.  */
  uerr_t type;

  //  DEBUGP (("parseurl (\"%s\") -> ", url));
  url += skip_url (url);
  recognizable = has_proto (url);
  if (strict && !recognizable)
    return URLUNKNOWN;
  for (i = 0, l = 0; i < ARRAY_SIZE (sup_protos); i++)
    {
      l = strlen (sup_protos[i].name);
      if (!strncasecmp (sup_protos[i].name, url, l))
	break;
    }
  /* If protocol is recognizable, but unsupported, bail out, else
     suppose unknown.  */
  if (recognizable && !sup_protos[i].name)
    return URLUNKNOWN;
  else if (i == ARRAY_SIZE (sup_protos))
    type = URLUNKNOWN;
  else
    u->proto = type = sup_protos[i].ind;

  if (type == URLUNKNOWN)
    l = 0;
  /* Allow a username and password to be specified (i.e. just skip
     them for now).  */
  if (recognizable)
    l += skip_uname (url + l);
  for (i = l; url[i] && url[i] != ':' && url[i] != '/'; i++);
  if (i == l)
    return URLBADHOST;
  /* Get the hostname.  */
  u->host = strdupdelim (url + l, url + i);
  //n  DEBUGP (("host %s -> ", u->host));

  /* Assume no port has been given.  */
  u->port = 0;
  if (url[i] == ':')
    {
      /* We have a colon delimiting the hostname.  It could mean that
	 a port number is following it, or a directory.  */
      if (ISDIGIT (url[++i]))    /* A port number */
	{
	  if (type == URLUNKNOWN)
	    u->proto = type = URLHTTP;
	  for (; url[i] && url[i] != '/'; i++)
	    if (ISDIGIT (url[i]))
	      u->port = 10 * u->port + (url[i] - '0');
	    else
	      return URLBADPORT;
	  if (!u->port)
	    return URLBADPORT;
	  DEBUGP (("port %hu -> ", u->port));
	}
      else if (type == URLUNKNOWN) /* or a directory */
	u->proto = type = URLFTP;
      else                      /* or just a misformed port number */
	return URLBADPORT;
    }
  else if (type == URLUNKNOWN)
    u->proto = type = URLHTTP;
  if (!u->port)
    {
      int i;
      for (i = 0; i < ARRAY_SIZE (sup_protos); i++)
	if (sup_protos[i].ind == type)
	  break;
      if (i == ARRAY_SIZE (sup_protos))
	return URLUNKNOWN;
      u->port = sup_protos[i].port;
    }
  /* Some delimiter troubles...  */
  if (url[i] == '/' && url[i - 1] != ':')
    ++i;
  if (type == URLHTTP)
    while (url[i] && url[i] == '/')
      ++i;
  u->path = (char *)xmalloc (strlen (url + i) + 8);
  strcpy (u->path, url + i);
  if (type == URLFTP)
    {
      u->ftp_type = process_ftp_type (u->path);
      /* #### We don't handle type `d' correctly yet.  */
      if (!u->ftp_type || toupper (u->ftp_type) == 'D')
	u->ftp_type = 'I';
    }
  DEBUGP (("opath %s -> ", u->path));
  /* Parse the username and password (if existing).  */
  parse_uname (url, &u->user, &u->passwd);
  /* Decode the strings, as per RFC 1738.  */
  decode_string (u->host);
  decode_string (u->path);
  if (u->user)
    decode_string (u->user);
  if (u->passwd)
    decode_string (u->passwd);
  /* Parse the directory.  */
  parse_dir (u->path, &u->dir, &u->file);
  DEBUGP (("dir %s -> file %s -> ", u->dir, u->file));
  /* Simplify the directory.  */
  path_simplify (u->dir);
  /* Remove the leading `/' in HTTP.  */
  if (type == URLHTTP && *u->dir == '/')
    strcpy (u->dir, u->dir + 1);
  DEBUGP (("ndir %s\n", u->dir));
  /* Strip trailing `/'.  */
  l = strlen (u->dir);
  if (l && u->dir[l - 1] == '/')
    u->dir[l - 1] = '\0';
  /* Re-create the path: */
  abs_ftp = (u->proto == URLFTP && *u->dir == '/');
  /*  sprintf (u->path, "%s%s%s%s", abs_ftp ? "%2F": "/",
      abs_ftp ? (u->dir + 1) : u->dir, *u->dir ? "/" : "", u->file); */
  strcpy (u->path, abs_ftp ? "%2F" : "/");
  strcat (u->path, abs_ftp ? (u->dir + 1) : u->dir);
  strcat (u->path, *u->dir ? "/" : "");
  strcat (u->path, u->file);
  URL_CLEANSE (u->path);
  /* Create the clean URL.  */
  u->url = str_url (u, 0);
  return URLOK;
}


int main(int argc,char **argv)
{
  char *url = "http://www.google.com";
  struct urlinfo *u;
  u = (struct urlinfo *)xmalloc(sizeof(struct urlinfo));
  memset(u,0,sizeof(*u));
  u -> proto = URLUNKNOWN;
  parseurl(url,u,0);
  
  return 0;
}
